#include <stdio.h>

#include "preq.h"
#include "processor.h"
#include "processor_simple.h"
#include "uarch/sim.h"

using namespace std;

extern Simulator Sim;

Processor_simple::Processor_simple (processor *proc)
    : Processor_context (proc)
{
}

Processor_simple::~Processor_simple (void)
{
}

void Processor_simple::tick (void)
{
    Insn *insn;

    for (int issued = 0; issued < ISSUE_WIDTH; issued++)
    {
        if (outstanding_reqs >= Sim.settings.mshrs_per_processor)
            break;

        if (thread->is_suspended () || thread->is_exited ())
            break;

        if (flush)
            break;
        
        insn = peek();
        if (insn)
        {
            assert (insn->is_memop ());
            dispatch_peek (insn->create_mreq ());
        }
        else
        {
            insn = fetch ();
            if (insn->is_memop ())
                dispatch (insn->create_mreq ());
        }
        delete insn;
    }
}

void Processor_simple::tock (Mreq *request)
{
    bool found;
    Preq *preq;
    int latency;
    LIST<Preq *>::reverse_iterator it;

    assert ((request->msg == C_DATA_S || request->msg == C_DATA_M) &&
            "Processor tock: illegal message received!");

    if (TICK_TOCK_DEBUG)
        request->print_msg (my_processor->moduleID, "Retiring");

    found = false;
    for (it = outstanding_req_list.rbegin(); it != outstanding_req_list.rend(); it++)
    {
        if ((*it)->addr == request->addr && !(*it)->resolved)
        {
            found = true;
            
            /** Requests should be being satisfied in time order, so if a 
                mismatch something broke!  */
            assert (request->preq && "Preq is NULL");
            assert (*it == request->preq && "Oldest request for this address doesn't match ack?");
            assert ((((*it)->msg == C_GETIM && request->msg == C_DATA_M) ||
                     ((*it)->msg == C_GETIS  && request->msg == C_DATA_S)) &&
                     "Request and Ack Mismatch?");

            (*it)->resolved = true;
            (*it)->resolve_time = Global_Clock;

            latency = Global_Clock - (*it)->req_time;

            my_processor->stats->mem_latency_hist.collect (latency);
            my_processor->stats->long_mem_latency_hist.collect (latency);

            if (latency > 10)
                my_processor->stats->L1_miss_mem_latency_hist.collect (latency);

            if (Sim.settings.LSQ_dependence)
            {
                /** Dont free the resources unless this is the oldest.  If so, also
                    need to free other old requests which were waiting on this one. */
                while (!outstanding_req_list.empty() && outstanding_req_list.back()->resolved)
                {
                    Preq *oldest_preq = outstanding_req_list.back();
                    my_processor->stats->LSQ_dependence_delay.collect (Global_Clock - oldest_preq->resolve_time);
                    outstanding_req_list.pop_back();
                    outstanding_reqs = outstanding_reqs - oldest_preq->req_count;
                    delete oldest_preq;
                }
            }
            else
            {
                /** Free the resources immediately. */
                preq = *it;
                my_processor->stats->LSQ_dependence_delay.collect (Global_Clock - preq->resolve_time);
                outstanding_reqs = outstanding_reqs - (*it)->req_count;
                outstanding_req_list.remove (*it);
                delete preq;
            }
            break;
        }
    }

    if (!found)
        fatal_error ("Received outstanding request reply, but was unable to find request.\n");

    /** Check to see if pipeline is empty after flush.  */
    if (flush && outstanding_reqs == 0)
    {
        flush = false;
        if (peeked)
        {
            Mreq *llsc;
            /** Emulate peeked instruction.  */
            llsc = fetch ()->create_mreq ();
            if (llsc->msg != LL && llsc->msg != SC)
                fatal_error ("Peek instruction not LL or SC");

            delete llsc;
            peeked = false;
        }
    }

    assert (outstanding_reqs >= 0 && "Context received more replies then requests!?!?");
}

/** If the thread has been de-scheduled and no more requests are outstanding. */
bool Processor_simple::context_can_be_released ()
{
    return (suspended && outstanding_req_list.empty());
}

/** Dispatch an instructions you've only peeked at.  */
void Processor_simple::dispatch_peek (Mreq *request)
{
    switch (request->msg)
    {
    case LL: request->msg = C_GETIS; break;
    case SC: request->msg = C_GETIM; break;
    default:
        fatal_error ("Unknown peeked instruction");
    }

    dispatch (request);

    /** After dispatching LL/SC set flush to true to disallow the issuing of 
        additional instructions.  */        
    flush = true;

    /** Setting peeked to true indicates the instruction should be emulated once 
        the outstanding request is resolved.  */
    peeked = true;
}

void Processor_simple::dispatch (Mreq *request)
{
    assert (request != NULL);
    assert (outstanding_reqs < Sim.settings.mshrs_per_processor);

    my_processor->stats->insn_count++;
    Sim.settings.sesc_nsim--;

    if (request->msg == NOP_INST)
    {
        delete request;
        return;
    }
    
    if (request->msg == SYNC)
    {
        if (outstanding_reqs > 0)
        {
            assert (flush == false);
            flush = true;
        }
        delete request;
        return;
    }

    assert (request->msg == C_GETIS || request->msg == C_GETIM);
    outstanding_reqs++;

    if (TICK_TOCK_DEBUG)
        request->print_msg (my_processor->moduleID, "Issuing");

    /** Traverse the outstanding_req_list to look for a match if found, 
        check for compatablity (load vs. store) increment count in the preq; 
        this is the MSHR (since ORL is req. time ordered, we don't have to 
        search the whole thing; first encounter is enough else create a new 
        preq, add to the list and launch the mreq
    */

    bool MSHR_hit;
    LIST<Preq *>::iterator it;

    MSHR_hit = false;
    for (it = outstanding_req_list.begin(); it != outstanding_req_list.end(); it++)
    {
        if ((*it)->addr == request->addr && !(*it)->resolved)
        {
            /** Check for MSHR hit based on Load vs Store.  */
            /** New request-centric logic. */

            /** TODO: Possible consistency issue here with Stores passing 
                other Stores to different addresses.  */

            if (request->msg == C_GETIS)
            {
                (*it)->req_count++;
                MSHR_hit = true;
                delete request;
                break;
            }

            if (request->msg == C_GETIM && (*it)->msg == C_GETIM)
            {
                (*it)->req_count++;
                MSHR_hit = true;
                delete request;
                break;
            }
        }
    }

    if (MSHR_hit == false)
    {
        /** Issue a new Preq and corresponding Mreq.  */
        Preq *p = new Preq (my_processor->moduleID, this, 
                            request->pc, request->addr, request->msg);
        request->preq = p;

        /** Add src and destination fields. */
        request->src_mid = my_processor->moduleID;
        request->dest_mid.nodeID = my_processor->moduleID.nodeID;
        request->dest_mid.module_index = L1_M;
        request->req_time = Global_Clock;
        outstanding_req_list.push_front (p);
        my_processor->write_output_port (request, Global_Clock + 1);
    }
}
